import * as step from '@flow-step/step-toolkit'
import * as fs from 'fs'
import {installKubectl} from './install'
import {getKubernetesCertificate} from './certificate'
import {hasFilesInDirectory} from './util'
import {IParams} from './params'
import path from 'path'
import {chmodSync} from 'fs'
import {polyglot} from './i18n'

const SEP = path.sep

export async function execute(params: IParams): Promise<void> {
    // install kubectl binary first
    let kubectlPath = ''
    try {
        const installPath = await installKubectl(params.kubectlVersion)
        kubectlPath = `${installPath}/kubectl-${params.kubectlVersion}`
    } catch (err) {
        throw new Error(`failed to install kubectl but err: ${err}`)
    }

    const kubernetesCertificate = await getKubernetesCertificate()
    params.clusterType = kubernetesCertificate.type

    step.infoV1('')
    step.infoV1('====================================')
    step.infoV1(`    CLUSTER TYPE ${params.clusterType}    `)
    step.infoV1('====================================')
    step.infoV1('')
    step.infoV1('[USER] KUBECTL_VERSION=' + params.kubectlVersion)
    step.infoV1('[USER] CLUSTER_TYPE=' + params.clusterType)
    step.infoV1('[USER] NAMESPACE=' + params.namespace)
    step.infoV1('[USER] PATH_TO_YAML=' + params.pathToYaml)
    step.infoV1('[USER] INSECURE_SKIP_TLS_VERIFY=' + params.insecureSkipTlsVerify)

    const kubeDirPath = `.kube_${Date.now()}`
    // 新建临时目录存储 kubeConfig 文件
    fs.mkdirSync(kubeDirPath, {recursive: true})
    const kubeConfigPath = `${kubeDirPath}/config`

    let kubectlArgs = ['']
    if (params.clusterType === 'custom' || !kubernetesCertificate.miranaProxy || kubernetesCertificate.miranaProxy === '') {
        if (kubernetesCertificate.kubeConfig === undefined || kubernetesCertificate.kubeConfig === '') {
            throw new Error(polyglot.t("failed_to_obtain_kubernetes_certificate_file"))
        }

        fs.writeFileSync(kubeConfigPath, kubernetesCertificate.kubeConfig)
        step.debug(`kubeConfig: ${kubernetesCertificate.kubeConfig}`)
        step.info(`save kubeConfig to ${kubeConfigPath} successfully!`)

        kubectlArgs = [`--kubeconfig=${kubeConfigPath}`, `--insecure-skip-tls-verify=${params.insecureSkipTlsVerify}`]
    } else {
        // 新建空的config文件，实际通过 kubernetesCertificate.miranaProxy / kubernetesCertificate.jwtToken 来发起请求
        fs.writeFileSync(kubeConfigPath, '')
        // Set insecure skip TLS verify for ACK mode
        params.insecureSkipTlsVerify = true

        kubectlArgs = [`--kubeconfig=${kubeConfigPath}`, `--server=${kubernetesCertificate.miranaProxy}`, `--token=${kubernetesCertificate.jwtToken}`, `--insecure-skip-tls-verify=${params.insecureSkipTlsVerify}`]
    }

    // 默认 params.namespace 为空，若 yaml 不填写 namespace，会复用环境变量里的值；私有构建机环境变量值为空则 namespace = default
    // 默认 params.namespace 为空，若 yaml 填写 namespace 则以 yaml 里 namespace 为准
    // 若 params.namespace 不为空，yaml 不填写 namespace  则 params.namespace 会生效到 yaml 上
    // 若 params.namespace 不为空，yaml 填写 namespace ，则 params.namespace 与 yaml namespace 必须相等，否则 kubectl --namespace=${params.namespace} 会报错
    if (params.namespace !== '') {
        kubectlArgs.push(`--namespace=${params.namespace}`)
    }

    step.infoV1("")
    step.infoV1(polyglot.t("kubernetes_cluster_basic_information"))
    step.infoV1("")
    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.join(' ')} version`)
    let resultCode = await step.exec.call(kubectlPath, kubectlArgs.concat(['version']), {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.infoV1(line)
        }
    })
    if (resultCode !== 0) {
        // print error log instead of throw new Error
        step.warnV1(`${kubectlPath} version execute failed with exit code: ${resultCode}`)
    }

    step.infoV1(polyglot.t("apply_manifests_definition_file"))
    const timestamp = new Date().getTime()
    const manifestFilePath = `${params.projectDir}/.manifest_${timestamp}`
    fs.mkdirSync(manifestFilePath, {recursive: true})

    step.infoV1("")
    step.infoV1(polyglot.t("create_cache_directory_for_publishing_resources", {manifest: manifestFilePath}))
    step.infoV1(`${params.images}`)

    // source file 直接chmod可能存在多个 Job 并发导致 text file busy 问题
    const outputs2templateSourceBin = __dirname + SEP + 'outputs2template'

    // copy a new binary
    const outputs2templateBin = path.join(params.workSpace, 'outputs2template')
    step.debug(`copy outputs2template binary: ${outputs2templateSourceBin} to ${outputs2templateBin}`)
    // 检查目标文件是否存在
    if (fs.existsSync(outputs2templateBin)) {
        step.debug(`remove outputs2templateBin: ${outputs2templateBin}`)
        fs.unlinkSync(outputs2templateBin);
    }
    fs.copyFileSync(outputs2templateSourceBin, outputs2templateBin)

    try {
        const stats = fs.statSync(outputs2templateBin)
        if ((stats.mode & 0o777) === 0o755) {
            step.debug(`Permissions for ${outputs2templateBin} is already 755`)
        } else {
            chmodSync(outputs2templateBin, 0o755)
            step.debug(`Permissions for ${outputs2templateBin} set to 755`)
        }
    } catch (err) {
        // @ts-ignore
        throw new Error(`Error setting file permissions: ${err.message}`)
    }

    const outputs2templateBinArgs = [`-data=${params.images}`, `-searchDir=${path.join(params.projectDir, params.pathToYaml)}`, `-tmpDir=${manifestFilePath}`, `-skipError=${params.skipUndefineVariable}`]
    step.debug(`exec command: ${outputs2templateBin} ${outputs2templateBinArgs.join(' ')}`)
    resultCode = await step.exec.call(outputs2templateBin, outputs2templateBinArgs, {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.warnV1(line)
        }
    })
    if (resultCode !== 0) {
        throw new Error(`outputs2template exec failed, exit code: ${resultCode}`)
    }

    step.infoV1(polyglot.t("yaml_template_processed_successfully", {pathToYaml: params.pathToYaml, manifest: manifestFilePath}))

    step.infoV1(polyglot.t("all_deployment_resources"))
    step.debug(`exec command: ls -lR ${manifestFilePath}`)
    resultCode = await step.exec.call('ls', ['-lR', manifestFilePath], {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.warnV1(line)
        }
    })
    if (resultCode !== 0) {
        throw new Error(`ls -lR ${manifestFilePath} failed, exit code: ${resultCode}`)
    }

    const hasFile = await hasFilesInDirectory(manifestFilePath)
    if (!hasFile) {
        throw new Error(polyglot.t("no_published_resources_found"))
    }

    step.infoV1(polyglot.t("list_of_resources_to_be_released"))

    // v1.16.4 等低版本不支持 --dry-run=client，默认 --dry-run=true 在高版本里被废弃但是保持兼容
    let dryRun = '--dry-run=true'
    if (params.kubectlVersion === 'v1.18.4') {
        dryRun = '--dry-run=client'
    }

    // 记录将发布的资源名称
    let resourceNames: string[] = []
    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.concat(['apply', '-f', manifestFilePath, dryRun]).join(' ')}`)
    resultCode = await step.exec.call(kubectlPath, kubectlArgs.concat(['apply', '-f', manifestFilePath, dryRun]), {
        listener: {
            stdoutLine: (line: string) => {
                const resourceName = line.split(' ')[0]
                if (resourceName !== '') {
                    step.infoV1(resourceName)
                    resourceNames.push(resourceName)
                }
            },
            stderrLine: (line: string) => step.warnV1(line)
        }
    })
    if (resultCode !== 0) {
        throw new Error(`kubectl apply -f ${manifestFilePath} ${dryRun} failed, exit code: ${resultCode}`)
    }

    step.debug(`resourceNames: ${resourceNames}`)

    let subCommand = 'apply'
    if (params.useReplace) {
        subCommand = 'replace'
    }

    step.infoV1("")
    step.infoV1(`${polyglot.t("deploy_changes_to_kubernetes_cluster")} kubectl ${subCommand} -f ${manifestFilePath}`)
    step.debug(`exec command: ${kubectlPath} ${kubectlArgs.concat([subCommand, '-f', manifestFilePath]).join(' ')}`)
    resultCode = await step.exec.call(kubectlPath, kubectlArgs.concat([subCommand, '-f', manifestFilePath]), {
        listener: {
            stdoutLine: (line: string) => step.infoV1(line),
            stderrLine: (line: string) => step.warnV1(line)
        }
    })
    if (resultCode !== 0) {
        throw new Error(`Command exec, exit code: ${resultCode}`)
    }

    if (params.namespace !== '') {
        step.infoV1("")
        step.infoV1(polyglot.t("print_the_current_task_execution_result"))

        for (const resource of resourceNames) {
            step.infoV1("")
            step.infoV1(`${resource}`)
            step.infoV1("")

            step.debug(`exec command: ${kubectlPath} ${kubectlArgs.concat(['get', resource]).join(' ')}`)
            await step.exec.call(kubectlPath, kubectlArgs.concat(['get', resource]), {
                listener: {
                    stdoutLine: (line: string) => step.infoV1(line),
                    stderrLine: (line: string) => step.warnV1(line)
                }
            })

            if (params.debug) {
                step.infoV1(polyglot.t("yaml_definition_of_online_resources"))

                step.debug(`exec command: ${kubectlPath} ${kubectlArgs.concat(['get', resource, '-o', 'yaml']).join(' ')}`)
                await step.exec.call(kubectlPath, kubectlArgs.concat(['get', resource, '-o', 'yaml']), {
                    listener: {
                        stdoutLine: (line: string) => step.infoV1(line),
                        stderrLine: (line: string) => step.warnV1(line)
                    }
                })
            }

        }
    }

    step.addOutput('RERUNABLE=true')

    step.infoV1(polyglot.t("clean_up_the_cache_directory", {manifest: manifestFilePath}))
    fs.rmSync(manifestFilePath, {recursive: true})
    // rm copy outputs2templateBin
    fs.unlinkSync(outputs2templateBin);
}